import argparse
import sys

from configuration_file_parser import parse_file
import plugin_system
from simulation import Simulation

import os

INTEGRATORS_DIR = "plugins" + os.sep + "integrator_plugins"
OUTPUTS_DIR = "plugins" + os.sep + "output_plugins"
INPUTS_DIR = "plugins" + os.sep + "input_plugins"


class SimulatorApplication(object):
    def __init__(self, integrator_dir, outputs_dir, inputs_dir):
        self.integrator_dir = integrator_dir
        self.outputs_dir = outputs_dir
        self.inputs_dir = inputs_dir

    def load_plugins(self):
        self.integrators = plugin_system.load_plugins(self.integrator_dir, plugin_system.Plugin)
        """
        if len(self.integrators) == 0:
            raise Exception, "Could not load integrator plugins from directory \"" + \
                self.integrator_dir + "\"."
        """

        self.outputs = plugin_system.load_plugins(self.outputs_dir, plugin_system.Plugin)
        """
        if len(self.outputs) == 0:
            raise Exception, "Could not load output plugins from directory \"" + \
                self.plugins_dir + "\"."
        """

        self.inputs = plugin_system.load_plugins(self.inputs_dir, plugin_system.Plugin)
        """
        if len(self.inputs_dir) == 0:
            raise Exception, "Could not load input plugins from directory \"" + \
                self.plugins_dir + "\"."
        """

    def print_keywords_descriptions(self):
        print "Simulation keywords: "
        print
        print "steps", "Number of steps for which to run simulation"

    def print_integrator_descriptions(self):
        print "Integrators:"
        for name, plugin in self.integrators.iteritems():
            print
            print name
            parameters = plugin.get_parameters()
            for param_name, param_desc in parameters.iteritems():
                param_type = param_desc[0]
                required = param_desc[1]
                desc = param_desc[2]
                if not required:
                    default = param_desc[3]
                    print param_name, "Not Required (default=" + str(default) + ")", desc
                else:
                    print param_name, "Required", desc

    def print_outputs_descriptions(self):
        print "Outputs:"
        for name, plugin in self.outputs.iteritems():
            print
            print name
            parameters = plugin.get_parameters()
            for param_name, param_desc in parameters.iteritems():
                param_type = param_desc[0]
                required = param_desc[1]
                desc = param_desc[2]
                if not required:
                    default = param_desc[3]
                    print param_name, "Not Required (default=" + str(default) + ")", desc
                else:
                    print param_name, "Required", desc

    def print_inputs_descriptions(self):
        print "Inputs:"
        for name, plugin in self.inputs.iteritems():
            print
            print name
            parameters = plugin.get_parameters()
            for param_name, param_desc in parameters.iteritems():
                param_type = param_desc[0]
                required = param_desc[1]
                desc = param_desc[2]
                if not required:
                    default = param_desc[3]
                    print param_name, "Not Required (default=" + str(default) + ")", desc
                else:
                    print param_name, "Required", desc

    def parse_conf_file(self, config_file):
        self.configuration = dict(parse_file(config_file))
        if "inputs" not in self.configuration:
            raise Exception, "Did not specify any inputs in configuration file!"
        elif "simulation" not in self.configuration:
            raise Exception, "Did not specify simulation section in configuration file!"
        elif "integrator" not in self.configuration:
            raise Exception, "Did not specify integrator in configuration file!"

    def initialize_integrator(self):
        if len(self.configuration["integrator"]) != 1:
            raise Exception, "Only one integrator can be specified.  You specified " + \
                str(len(self.configuration["integrators"])) + " integrators."
        for plugin_name, params in self.configuration["integrator"]:
            if plugin_name not in self.integrators:
                raise Exception, "Do not have an integrator with the name \"" + \
                    plugin_name + "\". Valid integrators are: " + ", ".join(self.integrators.keys())
            self.simulation.integrator = self.integrators[plugin_name](**dict(params))

    def initialize_outputs(self):
        if len(self.configuration["outputs"]) == 0:
            raise Exception, "Must specify at least one output."
        output_instances = []
        for plugin_name, params in self.configuration["outputs"]:
            if plugin_name not in self.outputs:
                raise Exception, "Do not have an output with the name \"" + \
                    plugin_name + "\". Valid outputs are: " + ", ".join(self.outputs.keys())
            output_instances.append(self.outputs[plugin_name](**dict(params)))
        self.simulation.outputs = output_instances

    def initialize_inputs(self):
        if len(self.configuration["inputs"]) == 0:
            raise Exception, "Must specify at least one input."
        input_instances = []
        for plugin_name, params in self.configuration["inputs"]:
            if plugin_name not in self.inputs:
                raise Exception, "Do not have an input with the name \"" + \
                    plugin_name + "\". Valid inputs are: " + ", ".join(self.inputs.keys())
            input_instances.append(self.inputs[plugin_name](**dict(params)))
        self.simulation.inputs = input_instances
    
    def initialize_simulation(self):
        params = dict(self.configuration["simulation"])
        if "steps" not in params:
            raise Exception, "Must provide number of steps in simulation section of configuration file. Only keywords provided are: " + str(params)
        try:
            steps = int(params["steps"])
        except Exception:
            raise Exception, "Convert not convert parameters \"steps\" with value (" + str(params["steps"]) + ") to integer."

        self.simulation = Simulation(steps=steps)

    def build_simulation(self, config_file):
        self.parse_conf_file(config_file)
        self.initialize_simulation()
        self.initialize_inputs()
        self.initialize_integrator()
        self.initialize_outputs()

    def simulate(self):
        self.simulation.run()
        
        
def parse_arguments(argv):
    parser = argparse.ArgumentParser(description="Solar system simulator.")
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("--integrators", dest="integrators", action="store_const", const=True, default=False, help="Display available integrators and their options.", required=False)
    group.add_argument("--outputs", dest="outputs", action="store_const", const=True, default=False, help="Display available outputs and their options.", required=False)
    group.add_argument("--inputs", dest="inputs", action="store_const", const=True, default=False, help="Display available inputs and their options.", required=False)
    group.add_argument("--keywords", dest="keywords", action="store_const", const=True, default=False, help="Display simulation keywords.", required=False)
    group.add_argument("--run", dest="config_file", action="store", default=None, help="Run the simulation specified in the configuration file.", required=False)
    args = parser.parse_args(argv)
    return args

def main(argv):
    args = parse_arguments(argv[1:])
    app = SimulatorApplication(INTEGRATORS_DIR, OUTPUTS_DIR, INPUTS_DIR)
    app.load_plugins()

    if args.integrators:
        app.print_integrator_descriptions()
    elif args.outputs:
        app.print_outputs_descriptions()
    elif args.inputs:
        app.print_inputs_descriptions()
    elif args.keywords:
        app.print_keywords_descriptions()
    else:
        app.build_simulation(args.config_file)
        app.simulate()
    

if __name__ == "__main__":
    main(sys.argv)

